home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Utilities Professional 1-1500
/
Utilities Professional 1-1500 (1994)(WPD)[!].iso
/
12511500
/
var1449.dms
/
var1449.adf
/
Devices
/
Devices.doc
< prev
next >
Wrap
Text File
|
1992-05-03
|
31KB
|
870 lines
1 DEVICES
1.1 INTRODUCTION
The Amiga's operating system must be able to handle input events
from the keyboard or gameports, monitoring the disk drivers,
sending data to the parallel device and communicating with the
serial port, playing stereo sound with its audio chips, etc...
If this was not enough, all these things have to be monitored
in a multitasking environment where several tasks (programs) may
run simultaneously.
As you understand this is a very complicated system that
controls all these things. Despite the complexity it must also
be very fast so as little processing time as possible is used.
The part of the Amiga which takes care of all these routines
is usually referred as "Exec". Exec handles tasks and task-
switching, interrupts, messages and controls all Amiga's
"devices".
In this chapter we will look at how you can, with help of Exec,
control the devices. A device is a collection of special
routines which handles things like disk drivers, serial and
parallel ports, input events, sound generating etc... Although
all these routines are very different they are all handled in
almost the same manner. This means that once you know how to
control one of the devices, you do not need to read much more
before you can control all of them.
1.2 REQUESTS
When you want to do something with a device you fill a "request
block" with your requirements and send it to Exec. Exec will
now take care of everything. Once it has, successfully or not,
completed your request it sends a message to you which tells
you that your request has been completed.
1.2.1 IOREQUEST
The "request block" is actually an IORequest structure defined
in header file "exec/io.h". It looks like this:
struct IORequest
{
struct Message io_Message;
struct Device *io_Device;
struct Unit *io_Unit;
UWORD io_Command;
UBYTE io_Flags;
BYTE io_Error;
};
io_Message: The top part of the request block consists of a
Message structure. It is used by Exec when the
request is sent around inside the Amiga. It is
also used to queue requests at a device's port.
You do not need to bother very much about this
part since Exec takes care of most things. You
must however initialize it correctly before you
may start to use the request.
io_Device: Pointer to the device which this request has been
prepared for. Se below for more information about
how to initialize a request block.
io_Unit: Some devices, the trackdisk device for example,
consists of several units (df0:, df1, and so on),
and this part is used to identify which unit should
be affected.
io_Command: It is here you set your command. There exist both
standard commands which are handle by most
devices, but there exist also a lot of special
unique commands for some devices. See below for
more information about available commands.
io_Flags: This field is used to set special flags.
io_Error: When the request has been completed you can look
at this field to see if your request was
successfully or not completed.
1.2.2 IOSTDREQ
The IORequest structure is the shortest request block you may
use, and consists of only the most important fields. Normally
you need to use a request block which is a bit larger. Since
it is the most commonly used request block, it is called
the "standard request block" (IOStdReq) and is defined like
this: (Also declared in the header file "exec/io.h")
struct IOStdReq
{
struct Message io_Message;
struct Device *io_Device;
struct Unit *io_Unit;
UWORD io_Command;
UBYTE io_Flags;
BYTE io_Error;
ULONG io_Actual;
ULONG io_Length;
APTR io_Data;
ULONG io_Offset;
};
As you can see the top part is identical to the IORequest
structure, but there have been four extra fields added.
io_Actual: When your request has been complete you can look
here to find the actual number of bytes transferred.
If this value is not the same as the number of
bytes you wanted to transfer you know something
went wrong.
io_Length: The number of bytes you want to transfer. If you
want to continue to send/retrieve information until
some special external event, set this field to -1.
io_Data: If you are going to send (write) data, this should
be a pointer to the data that should be sent. If you
want to retrieve (read) data this should be a
pointer to a memory buffer were all data which is
collected should be store.
io_Offset: Special offset value used by some devices.
To make life simpler there exist a special function which will
automatically allocate and preinitialize the request block. The
function is called CreateStdIO(). However, before you may use
this function you have to create a message port to which the
device can communicate with you. Simply use the CreatePort()
function which is described in the "System" manual, chapter
"Messages".
CreatePort() allocates and initializes a MsgPort structure.
Normally the message port should only be used by you and the
device, so you do not need to give it any name. The priority
should usually be set to 0 (normal priority).
Synopsis: msg_port = CreatePort( name, pri );
msg_port: (struct MsgPort *) Pointer to the new MsgPort
structure, or NULL if something went wrong.
name: (char *) Pointer to a string containing the name
of the message port, or NULL. When working with
devices you normally do not need to use any name.
msgp: (struct MsgPort *) Pointer to the MsgPort structure
that should be allocated.
pri: (BYTE) This message port's priority. Usually set
to 0 (normal priority).
Once you have successfully created the reply port you can
allocate and initialize the standard request block with help
of the CreateStdIO() function. Give it a pointer to the
reply port as the only parameter.
Synopsis: std_req = CreateStdIO( msg_port );
std_req: (struct IORequest *) Pointer to the new standard
request block (struct IOStdReq *), or NULL if the
request block could not be created.
msg_port: (struct MsgPort *) Pointer to the message port
the device should use to communicate with you.
When your program terminates you must delete all request
blocks you have created. Use the DeleteStdIO() function.
Synopsis: DeleteStdIO( std_req );
std_req: (struct IOStdReq *) Pointer to the standard
request block you want to delete.
All message ports must also be deleted, but remember to first
deallocate the standard request block to which the message
port is connected to, before you delete the message port itself.
Synopsis: DeletePort( msg_port );
msg_port: (struct MsgPort *) Pointer to the MsgPort structure
that should be deallocated.
Here is a short example:
/* Pointer to the standard request block: */
struct IOStdReq *std_req;
/* Pointer to the message (reply) port: */
struct MsgPort *msg_port;
/* 1. Create a message port so the device can */
/* communicate with us: (No name, normal priority) */
msg_port = CreatePort( NULL, 0 );
if( !msg_port )
clean_up( "ERROR! Could not create message port!" );
/* 2. Allocate and initialize a new standard request block. */
/* It should use our new message port as reply port: */
std_req = CreateStdIO( msg_port );
if( !std_req )
clean_up( "ERROR! Could not allocate the request block!" );
...
/* Deallocate standard request block: */
DeleteStdIO( std_req );
/* Close message port: */
DeletePort( msg_port );
1.2.3 IOEXTREQ
As if the extra IOStdReq structure would not be enough. There
exist an even larger request block, usually referred as the
"extended request block". This request structure always looks
different depending on which device it is used for. It always
consists of a IORequest structure, or at least the same fields,
at the top, but the rest may vary. See the following chapters
for more information about this request block.
Since the size of this extended request block varies you can
not use the CreateStdIO() function to create and initialize it.
Instead you have to use the CreateExtIO() function which will
allocate a request block of any specified size. Except of
allocating a larger request block it will do exactly the same
things as CreateStdIO(). (Remember that you first have to
create a message (reply) port as described above.)
Synopsis: ext_req = CreateExtIO( msg_port, size );
ext_req: (struct IORequest *) Pointer to the new extended
request block, or NULL if the request block could
not be created.
msg_port: (struct MsgPort *) Pointer to the message port
the device should use to communicate with you.
size: (long) The number of bytes that should be allocated
for the extended request block. Use the function
sizeof() to find the exact number of bytes needed.
When your program terminates all request block have to be
deleted. Extended request blocks are deleted with the special
DeleteExtIO() function. Once the request block has been deleted
you should also delete the message (reply) port.
Synopsis: DeleteExtIO( std_req, size );
std_req: (struct IOStdReq *) Pointer to the extended
request block you want to delete.
size: (long) The size of the request block, in bytes.
Here is a short example: (In this example we will use an
extended request block designed for the "serial device".)
/* Pointer to the extended request block: */
struct IOExtSer *ext_req;
/* Pointer to the message (reply) port: */
struct MsgPort *msg_port;
/* 1. Create a message port so the device can */
/* communicate with us: (No name, normal priority) */
msg_port = CreatePort( NULL, 0 );
if( !msg_port )
clean_up( "ERROR! Could not create message port!" );
/* 2. Allocate and initialize a new extended request block. */
/* It should use our new message port as reply port, and */
/* have the same size as an IOExtSer structure: */
ext_req = CreateExtIO( msg_port, sizeof( struct IOExtSer ) );
if( !ext_req )
clean_up( "ERROR! Could not allocate the request block!" );
...
/* Deallocate extended request block: (Must be the same */
/* size as when allocated!) */
DeleteExtIO( ext_req, sizeof( struct IOExtSer ) );
/* Close message port: */
DeletePort( msg_port );
1.3 PREPARE THE DEVICE
Once you have created a request block you may "open" the device
to which the request block should be connected to. Note that
some devices only need a standard request block (or just a
IORequest structure), but others may need larger extended
request blocks. See the following chapters for more information.
To open a device simply use the OpenDevice() function.
Synopsis: error = OpenDevice( name, unit, req, flags );
error: (long) If OpenDevice() managed to open the device
it returns 0, else an error number is returned.
name: (char *) Name of the device you want to open.
See the following chapters for more information.
unit: (long) Which unit you want to open. Some devices
does not have several units and this field is then
ignored.
req: (struct IORequest *) Pointer to a request block.
Note that some devices may need a larger extended
request blocks.
flags: (long) Special flags may sometimes be used here,
otherwise this field is ignored.
As usual, everything that you open must be closed before the
program may terminated. To close a device simply call the
CloseDevice() function. After you have closed the device you
should delete the request block and then the message port.
(Unless you want to reopen the device later on.)
Synopsis: CloseDevice( req );
reg: (struct IORequest *) Pointer to the device's
request block.
1.4 STANDARD EXEC COMMANDS
There exist eight standard commands which most devices
understand. If the device is already occupied by some other
request, your request is queued on a FIFO basis (First In
First Out). For some devices it may happen that the other
requests are aborted and your started if your request has
higher priority.
The eight "standard" commands are:
CMD_RESET This command will reset the device. All currently
queued requests are removed (aborted) and the
device is reinitialized to the default values.
CMD_READ This command tells the device to start reading
(collecting) data. The "io_Length" field of the
request block states how many bytes should be
read, and the "io_Actual" field will contain the
number of bytes actually transferred.
CMD_WRITE This command tells the device to start writing
(sending) data. The "io_Length" field of the
request block states how many bytes should be
written, and the "io_Actual" field will contain
the number of bytes actually transferred.
CMD_UPDATE This command will update a device in that sense
that any values still in the internal buffers are
written out. Most times this is automatically
done, but this command may be needed when you
are working with the "trackdisk device" for
example.
CMD_CLEAR This command will clear any internal memory
buffers.
CMD_STOP When this command is sent to a device all
communication to and from that device will be
temporarily halted. Use the "CMD_START" command
to restart the communication.
CMD_START This command tells the device to restart the
communication which has temporarily been halted
by a "CMD_STOP" command.
CMD_FLUSH If this command is sent to a device all currently
queued requests will be removed (aborted).
There exist several more commands, but those are device
depending, and may therefore only be used with a certain
device. See the following chapters for more information about
these special device commands.
1.5 SEND REQUESTS
Once you have initialized the request block and "opened" the
desired device, which is described in more details in the
following chapters, you only have to send the request to Exec
and the rest will automatically be done for you. (Isn't that
nice! At last you do not need to worry about everything.)
When you send requests to a device the commands can either be
synchronous or asynchronous. When you send synchronous commands
your program is halted until the request has been completed.
With asynchronous commands your program will continue to run
while the device is executing your request.
1.5.1 SYNCHRONOUS COMMANDS
Synchronous commands are very easy to handle. You simply send
your request with the DoIO() function. Your program will
automatically be put to sleep while the device is executing
your command, and once it has finished your program will
wake up. Since your program is put to sleep ("task sleep")
no processing time is waisted while you are waiting for your
request to be completed.
Synopsis: error = DoIO( req );
error: (long) DoIO() will return first when the request has
been completed or something has failed. If the
request was successfully completed zero is returned,
else an error number is returned. What error number
depends on which device was used. See below for a
complete list of standard error commands.
req: (struct IORequest *) Pointer to the request you
want to have executed.
1.5.2 ASYNCHRONOUS COMMANDS
Asynchronous requests are a bit more complicated to handle.
Since your program will continue to run while your request is
being executed you do not know when it has been completed.
Asynchronous requests can be very useful when you do not want
to pause the program while waiting. (It would not be a very
nice game if it was halted each time the user did not move the
joystick for example.)
You start asynchronous requests with help of the SendIO()
function.
Synopsis: SendIO( req )
req: (struct IORequest *) Pointer to the request you
want to have executed.
After you have issued an asynchronous request there exist
three different ways to find out when it has been completed.
1.5.2.1 KEEP ON CHECKING THE REQUEST UNTIL IT HAS BEEN COMPLETED
You can use the CheckIO() function which will either return
NULL if the request has not been completed, or it will return
a pointer to the request if it has been completed.
Synopsis: ptr = CheckIO( req );
ptr: (long) CheckIO() will either return NULL if the
request have not been completed or it will return a
pointer to the request block.
req: (struct IORequest *) Pointer to the request you
want to check.
When a request has been completed a message is sent to a
specified reply port. Note that CheckIO() will not remove the
message at the reply port. This should be done with the
Remove() function.
Synopsis: Remove( node );
node: (struct Node *) Pointer to the node that should be
removed.
1.5.2.2 WAIT FOR THE REQUEST TO BE COMPLETED
You can put your program to sleep and wait for the request to
be completed, by calling the WaitIO() function. WaitIO() will
automatically remove the message at the reply port.
Synopsis: error = WaitIO( req );
error: (long) WaitIO() will return first when the request,
that has previously been sent, has been completed or
something has failed. If the request was successfully
completed zero is returned, else an error number is
returned. What error number depends on which device
was used.
req: (struct IORequest *) Pointer to the request you
want to wait for to be completed. Note that the
request must have already been sent to the device
by either a SendIO() or BeginIO() function call. If
the request has already been completed WaitIO() will
immediately return.
1.5.2.4 WAIT FOR A REPLY MESSAGE
Since a message is sent to the request block's reply port each
time the request has been completed you can simply wait for a
message to arrive. The simplest way is to use the WaitPort()
function which will put your program to sleep while waiting
for a message to arrive at the specified port. Note that it
will not remove the message.
Synopsis: msg = WaitPort( msg_port );
msg: (struct Message *) Pointer to the first message which
has arrived. Remember that this function will not
remove the message from the queue. Use the Remove()
function to do this.
msg_port: (struct MsgPort *) Pointer to the message port which
you want to monitor. Your program will be put to
sleep while waiting, and if no message arrive it
will never wake up, so be careful.
Instead of just waiting for one request to be completed you
can wait fore several ones at the same time. This is extremely
useful if your program is handling a lot of requests
simultaneously. Now we need to use the multipurpose Wait()
function which is described in the "System" manual, chapter
"Messages".
Synopsis: rsig = Wait( wsig )
rsig: (ULONG) When one of the specified signal bits was
received, the task is woken up and Wait() returns
the signal bit value that woke it up.
wsig: (ULONG) The signals we are waiting for. Wait() will
put the task to sleep, and it will only wake up if
one of the specified signal bit values is received.
1.5.4 QUICK I/O
Although the system with sending requests and messages is very
fast it may sometimes not be fast enough. If you want to handle
a lot of data extremely fast you can try to use the "Quick
mode". When you are using this quick mode Exec tries to skip
all routines that are not essential. No messages are sent, and
there is no checking etc...
The quick mode should only be used when really needed, and it
can not always be used. When you want to try to use the quick
mode set the IOF_QUICK flag in the "io_Flags" field of the
request block. To pass the request to Exec you should not use
the DoIO() or SendIO() functions, but instead the low level
function BeginIO().
Synopsis: BeginIO( req )
req: (struct IORequest *) Pointer to the request you
want to have executed. When you want to use the
quick mode you have to use this low level function
rather than SendIO() and DoIO().
If we were allowed to use the quick mode the request will be
synchronous. (Your program is put to sleep since all computer
time is needed to run this fast quick mode.)
If we were not allowed to use the quick mode the request will
be asynchronous, and is then handled exactly the same as if the
request had been sent by a SendIO() call.
After you have posted your quick request you must check if you
were allowed to use this fast mode or not. If not you have to
wait for the request to be completed and remove the reply
message yourself. To check if you were allowed quick mode or
not simply look at the "io_Flags" field of the request block to
see if the flag IOF_QUICK is still there or not. If the flag is
still set the request was using the quick mode, and you do not
need to do anything more. If the flag is not there any more,
you were not allowed quick mode, and thus you have to wait for
the asynchronous request to be completed and remove the message
yourself.
/* Initialize the request block as normal. */
/* The pointer to it is called "ioreq". */
/* ... */
/* Try to use the "quick" mode: */
ioreq->IOSer.io_Flags = IOF_QUICK;
/* Do the request: */
BeginIO( ioreq );
/* Check if the flag IOF_QUICK is still there or not: */
if( (ioreq->IOSer.io_Flags & IOF_QUICK) )
{
/* OK! The request was using quick mode, which means */
/* that our task was put to sleep while the request was */
/* executed, and the request has now been completed. */
/* Since we are using quick mode there are no message */
/* that should be removed. */
}
else
{
/* Too bad, we were not allowed to use the quick mode. */
/* The request should now be treated as if it was */
/* started with a SendIO() function call. This request */
/* is asynchronous - request returns immediately. */
/* Wait for the request to be completed. WaitIO() will */
/* also automatically remove the message at the reply */
/* port. */
WaitIO( ioreq );
}
1.5.3 ABORT REQUESTS
You can abort a previously started request by calling the
AbortIO() function. Of course, only asynchronous commands can
be aborted, and if the request has already been completed it
can not be aborted any more.
Synopsis: AbortIO( req )
req: (struct IORequest *) Pointer to the request you
want to abort.
1.6 ERRORS
No one is perfect, and even the devices may sometimes not be
able to do what you tell them to do (usually because you have
done something wrong). If something goes fails an error number
is returned. Each device has it own set of unique error codes
which are described in the following chapters. There exist,
however, four error codes that are sent by Exec. (They are all
defined in the header file "exec/errors.h")
IOERR_OPENFAIL The device (unit) could not be opened. If you
receive this message there is normally some
other program currently using the device with
exclusive mode.
IOERR_ABORTED When you abort a previously started request by
calling the AbortIO() function, the io_Error
filed of that request is set to IOERR_ABORTED.
If you find a request block with this flag set,
you know that it has been aborted.
IOERR_NOCMD You tried to use a command that is not
supported by that device.
IOERR_BADLENGTH The length of the request was not valid.
1.7 DEVICES
There exist a lot of different devices each specialized to do
something unique. Some devices are so called "low level
devices" and are handling a lot of fresh untranslated raw
data which will be modified and later used by other so called
"high level devices".
In total there exist 12 devices (14 with the new V2.05 operating
system) which all will be described in a chapter each. Although
the devices are so different, they all are handled in much the
same manner - allocate a message port and a request block, open
the device and start to send your request, finally close
everything and quit.
Five devices are not covered in this edition, but will soon be
added. All registered members will receive more information
about this as soon as the chapters have been completed.
1.7.1 Timer Device
The Timer Device can be used if you want to receive a message
after a specified time limit. This is one of the so called "low
level devices", and is used by many other devices. Although the
timer device does not sound very interesting, it can really be
useful.
1.7.2 Gameport Device
The Gameport Device is a smart little device which reads data
from the two gameport connectors on the Amiga. The device
can handle both normal Joystick as well as Mouse movements.
Even light-pens and other input devices may be connected and
monitored by this device. These special input devices must
however have its own drivers which translates the signals to
for the gameport device understandable form.
1.7.3 Audio Device
The Audio Device is used to produce sound. It monitors the four
hardware sound channels, and can therefore play the sound in
stereo. Everything from a simple beep to complete songs may be
played. The audio device can both produce sounds itself or
directly play sampled (digitized) sound effects.
Sound is an important part of our life and should therefore
in my opinion also be used a lot in computer programs. Sound
can be used in most situations, from emergency beeps to
entertaining melodies.
1.7.4 Narrator Device
The Amiga is not only extremely good at producing high quality
sound. with help of the Narrator Device it can also speak
almost as a human. Although it is not a Frank Sinatra, it is
possible to understand what it is saying, and this can be used
in many situations. Used with care, it is possible to produce
very good sound.
Sadly, somehow many persons do not like this unique feature of
the Amiga, and very few programs support the narrator device. I
believe that artificial speech can be used in many situations,
and can really be handy for the user. Most "professional"
programs would really benefit from using the narrator device.
1.7.5 Trackdisk Device
With help of the Trackdisk Device you can directly access the
disk drivers. Data can not only be transferred to and from the
disks, but you may even format specific sectors of a disk and
do a lot of other low level stuff.
1.7.6 Serial Device
The Serial Device is used to transfer data from and to the
serial port. It can be used at different speeds (bauds) and
use seven or eight bit characters. The device also support
parity (error) checking.
1.7.7 Parallel Device
The Parallel device is used to transfer data from and to the
parallel port. The parallel device is very useful when you
want very fast communications, and is therefore often used by
sound samplers and video digitizers. However, the most commonly
used external device is without question a printer, but you
should then use the printer device which has specially been
designed for this purpose.
1.7.8 Printer Device
The printer device can use both the serial and parallel device
depending on to which port the printer is connected to. With
the printer device you can not only print normal text, special
printer commands may be sent and you can even print graphics
with it.
The printer device uses the preferences settings to find out
to which port the printer is connected, what printer it is,
and other special settings like margins, paper length etc...
1.7.9 Keyboard Device
The Keyboard Device is a "low level device" which is used by
the "Input Device". The keyboard device collects untranslated
(raw) key codes. This device is very fast and uses very little
computer time.
This device will be covered in the next version of the manual.
All registered members will receive more information as soon as
the next version has been completed.
1.7.10 Input Device
The Input Device is a so called "high level device" which
receives information from the timer, keyboard and gameport
devices, and puts all this information into one single input
stream. Very handy and useful device.
This device will be covered in the next version of the manual.
All registered members will receive more information as soon as
the next version has been completed.
1.7.11 Console Device
The Console Device enables you to handle input and output as
an intelligent terminal for ASCII characters. Works very close
with Intuition, and is also a very handy and useful device.
This device will be covered in the next version of the manual.
All registered members will receive more information as soon as
the next version has been completed.
1.7.12 Clipboard Device
With the Clipboard Device data can be cut, pasted and copied
between different programs. Several clips may be used
simultaneously, and the device supports the IFF standard.
This device will be covered in the next version of the manual.
All registered members will receive more information as soon as
the next version has been completed.
1.7.13 SCSI AND PCMCIA DEVICE
There exist two new devices. The SCSI device is only available
with the new V2.0 operating system, and the PCMCIA device exist
for the moment only on Amiga 600.
These device will be covered in the next version of the manual.
All registered members will receive more information as soon as
the next version has been completed.